#include "net/socket_opt.h"
#include "net/socket.h"
#include "net/buffer.h"
#include <sys/socket.h>
#include <sys/types.h>

using namespace net;
namespace sock {

int32 CreateSocket() {
  int32 ret = ::socket(AF_INET, SOCK_STREAM, 0);
  if (ret == -1) {
    PLOG(WARNING)<< "socket error";
    return -1;
  }

  LOG(INFO)<< "create socket, fd: " << ret;
  return ret;
}

void Close(int sockfd) {
  if (sockfd != -1) {
    ::close(sockfd);
    LOG(INFO)<< "close socket, fd: " << sockfd;
  }
}

bool Bind(net::Socket* s, const net::Addr& addr) {
  int32 ret = ::bind(s->Fd(), (struct sockaddr*) &addr.addr, sizeof(addr.addr));
  if (ret == -1) {
    PLOG(WARNING)<< "socket bind error";
    return false;
  }

  if (TrunOnOpt(s, SOL_SOCKET, SO_REUSEADDR)) {
    return false;
  }
  if (TrunOnOpt(s, SOL_SOCKET, SO_KEEPALIVE)) {
    return false;
  }

  return true;
}

#define BACK_LOG 1024

bool Listen(net::Socket* s) {
  if (s == NULL) return false;
  int32 ret = ::listen(s->Fd(), BACK_LOG);
  if (ret == -1) {
    PLOG(WARNING)<< "listen error";
    return false;
  }

  return true;
}

int32 Accept(net::Socket* s, net::Addr* addr) {
  CHECK(s != NULL && addr != NULL);

  socklen_t len = sizeof(struct sockaddr_in);
  int32 ret;
  do {
    ret = ::accept4(s->Fd(), (struct sockaddr*) &addr->addr, &len,
                    SOCK_NONBLOCK | SOCK_CLOEXEC);
  } while (ret == -1 && errno == EINTR);
  if (ret > 0) {
    LOG(INFO)<< "accept from: " << addr->DebugString();
    return ret;
  }

  PLOG(WARNING)<< "accept4 error, accept fd: " << s->Fd();
  return -1;
}

bool Connect(net::Socket* s, const net::Addr& addr) {
  int32 ret;
  do {
    ret = ::connect(s->Fd(), (struct sockaddr*) &addr.addr, sizeof(addr.addr));
  } while (ret == -1 && errno == EINTR);

  if (ret == -1) {
    PLOG(WARNING)<< "connect error, fd: " << s->Fd();
    return false;
  }

  return true;
}

bool SetSockOpt(net::Socket* s, int32 level, int32 opt, void* val, size_t len) {
  if (s == NULL || s->Fd() < 0 || val == NULL) return false;
  int32 ret = ::setsockopt(s->Fd(), level, opt, val, len);
  if (ret == -1) {
    PLOG(WARNING)<< "setsockopt error";
    return false;
  }

  return true;
}

bool TrunOnOpt(net::Socket* s, int32 level, int32 opt) {
  int32 val = 1;
  return SetSockOpt(s, level, opt, &val, sizeof(val));
}

bool TrunOffOpt(net::Socket* s, int32 level, int32 opt) {
  int32 val = 0;
  return SetSockOpt(s, level, opt, &val, sizeof(val));
}

net::Addr GetSockAddr(net::Socket* s, bool is_local) {
  net::Addr addr;
  int32 ret, len = sizeof(struct sockaddr_in);
  if (!is_local) {
    ret = ::getpeername(s->Fd(), (struct sockaddr*) &addr.addr,
                        (socklen_t *) &len);
  } else {
    ret = ::getsockname(s->Fd(), (struct sockaddr*) &addr.addr,
                        (socklen_t *) &len);
  }

  PCHECK(ret != -1 && len == sizeof (struct sockaddr_in))
      << "getsockname error";
  return addr;
}

ssize_t Recv(const net::Socket* sock, net::Buffer* buf) {
  return -1;
}

ssize_t Send(const net::Socket* sock, const u_char* buf, size_t buflen) {
  CHECK(sock != NULL && buflen > 0);
  int32 ret;
  do {
    ret = ::send(sock->Fd(), buf, buflen, 0);
  } while (ret == -1 && errno == EINTR);

  if (ret == -1) {
    PLOG(WARNING)<< "write sock error, fd: " << sock->Fd();
  }
  return ret;
}

ssize_t Send(const net::Socket* sock, net::Buffer* buf) {
  CHECK(sock != NULL && buf != NULL);
  int32 ret;
  size_t len = buf->ReadableSize();
  do {
    ret = ::send(sock->Fd(), buf->Peek(), len, 0);
  } while (ret == -1 && errno == EINTR);

  if (ret == -1) {
    PLOG(WARNING)<< "send socker error: " << sock->Fd();
    return -1;
  }

  buf->Retrieve(ret);
  return ret;
}
}
